﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NLog;
using TradingServer.Core.Collections;

namespace TradingServer.Core.Entities
{
	public class OrderGlass
	{
		public OrderGlass(Security security)
		{
			_security = security;
		}

		public void Put(Order order)
		{
			lock (ThisLock)
			{
				Lookup(order);
				if (order.RemainingVolume > 0)
					AddToQueue(order);
			}
		}

		public void Clear()
		{
			lock (ThisLock)
			{
				SellQueue = new ConcurrentList<Order>();
				BuyQueue = new ConcurrentList<Order>();
			}
		}

		public void SubscribeQuotations(Guid receiver, Action<Quotation> callback)
		{
			Thread subscriberThread = new Thread(QuotationSubscription);
			_quotationSubsribers.Add(receiver, subscriberThread);
			subscriberThread.Start(new Tuple<Action<Quotation>, Guid>(callback, receiver));
		}

		public void UnsubscribeQuotations(Guid receiver)
		{
			_quotationSubsribers.Remove(receiver);
		}

		public void SubscribeCandles(Guid receiver, Action<Candle> callback, TimeSpan interval)
		{
			Thread subscriberThread = new Thread(CandleSubscription);
			_candleSubsribers.Add(receiver, subscriberThread);
			subscriberThread.Start(new Tuple<Action<Candle>, TimeSpan, Guid>(callback, interval, receiver));
		}

		public void UnsubscribeCandles(Guid receiver)
		{
			_candleSubsribers.Remove(receiver);
		}

		public event Action<Trade, EventArgs> TradeCreated;

		/// <summary>
		/// Часть стакана "продажа", ключ - цена, значение пары - объем торгов по этой цене
		/// </summary>
		public IOrderedEnumerable<KeyValuePair<decimal, decimal>> Sales { get { return GetGlassPart(SellQueueClone).OrderByDescending(item => item.Key); } }

		/// <summary>
		/// Часть стакана "покупка", ключ - цена, значение пары - объем торгов по этой цене
		/// </summary>
		public IOrderedEnumerable<KeyValuePair<decimal, decimal>> Buyings { get { return GetGlassPart(BuyQueueClone).OrderByDescending(item => item.Key); } }

		#region Internal & Protected members

		internal void Remove(Order order)
		{
			if (order.RemainingVolume == 0) return;
			lock (ThisLock)
			{
				if (order.RemainingVolume == 0) return;
				switch (order.Type)
				{
					case OrderType.Buy:
						if (!BuyQueue.Remove(order))
							Log.Warn("Order {0} not removed", order.Id);
						break;
					case OrderType.Sell:
						if (!SellQueue.Remove(order))
							Log.Warn("Order {0} not removed", order.Id);
						break;
				}
			}
		}

		#endregion

		#region Private members

		private static readonly Logger Log = LogManager.GetCurrentClassLogger();

		protected readonly object ThisLock = new object();

		private readonly Dictionary<Guid, Thread> _candleSubsribers = new Dictionary<Guid, Thread>();
		private readonly Dictionary<Guid, Thread> _quotationSubsribers = new Dictionary<Guid, Thread>();
		private readonly object _tradeLock = new object();
		private volatile Trade _lastTrade = null;

		private Security _security;
		private ConcurrentList<Order> _sellQueue = new ConcurrentList<Order>(); // очередь заявок на продажу
		private ConcurrentList<Order> SellQueue 
		{ 
			get { return _sellQueue; }
			set { _sellQueue = value; } 
		}
		private IEnumerable<Order> SellQueueClone
		{
			get { return new List<Order>(_sellQueue); }
		}

		private ConcurrentList<Order> _buyQueue = new ConcurrentList<Order>(); // очередь заявок на покупку
		private ConcurrentList<Order> BuyQueue 
		{ 
			get { return _buyQueue; }
			set { _buyQueue = value; }
		}
		private IEnumerable<Order> BuyQueueClone
		{
			get { return new List<Order>(_buyQueue); }
		}

		private void Lookup(Order order)
		{
			// order by market price
			List<Order> queue = GetQueue(order);
			if (queue == null || queue.Count == 0) return;

			Func<decimal, decimal, bool> func = (buyPrice, sellPrice) => { return order.Type == OrderType.Buy ? buyPrice >= sellPrice : buyPrice <= sellPrice; };
			var list = queue.Where(
					item => item.RemainingVolume > 0
					        && item.State == OrderState.Opened
					        && (item.Price == null || order.Price == null || func(order.Price.Value, item.Price.Value))).OrderBy(item => item.Price);
			foreach(var item in list)
			{
				if (item != null) CreateTrade(order, item);
				if (order.RemainingVolume == 0) break;
			};
		}

		private void CreateTrade(Order searching, Order forMatching)
		{
			decimal price = forMatching.Price.HasValue
			                 	? (searching.Price.HasValue
									? Math.Min(forMatching.Price.Value, searching.Price.Value)
									: forMatching.Price.Value)
			                 	: searching.Price.Value;

			Trade trade = new Trade(searching.Security, Math.Min(searching.RemainingVolume, forMatching.RemainingVolume), price);

			// Информарием подписчиков о новой сделке
			OnTradeCreated(trade);

			if (searching.Type == OrderType.Sell)
			{
				trade.SellOrder = searching;
				trade.BuyOrder = forMatching;
			}
			else
			{
				trade.SellOrder = forMatching;
				trade.BuyOrder = searching;
			}
			searching.AddTrade(trade);
			forMatching.AddTrade(trade);
		}

		private void OnTradeCreated(Trade trade)
		{
			TradeCreated.SafeInvoke(trade, new EventArgs());

			_lastTrade = trade;
			lock (_tradeLock) Monitor.PulseAll(_tradeLock);
		}

		private List<Order> GetQueue(Order order)
		{
			List<Order> queue = null;
			switch (order.Type)
			{
				case OrderType.Buy:
					queue = SellQueueClone.Where(item => item != null).OrderBy(item => item).ToList();
					break;
				case OrderType.Sell:
					queue = BuyQueueClone.Where(item => item != null).OrderByDescending(item => item).ToList();
					break;
			}
			return queue;
		}

		private void AddToQueue(Order order)
		{
			if (order.RemainingVolume > 0)
			{
				// ставим заявку в очередь как не до конца исполненную
				switch (order.Type)
				{
					case OrderType.Buy:
						BuyQueue.Add(order);
						break;
					case OrderType.Sell:
						SellQueue.Add(order);
						break;
				}
			}
		}

		private static SortedList<decimal, decimal> GetGlassPart(IEnumerable<Order> list)
		{
			var grouping = list.Where(item => item != null && item.Price.HasValue && item.State == OrderState.Opened).GroupBy(item => item.Price.Value).Select(x => new
			{
				Price = x.Key,
				Volume = x.Sum(o => o.Volume)
			});
			var dict = new SortedList<decimal, decimal>();
			foreach (var record in grouping) dict.Add(record.Price, record.Volume);
			return dict;
		}

		private void QuotationSubscription(object data)
		{
			Tuple<Action<Quotation>, Guid> threadData = data as Tuple<Action<Quotation>, Guid>;
			Action<Quotation> callback = threadData.Item1;
			Guid receiver = threadData.Item2;

			while (_quotationSubsribers.ContainsKey(receiver))
			{
				lock (_tradeLock) Monitor.Wait(_tradeLock, TimeSpan.FromSeconds(1));
				if (_lastTrade == null) continue;

				Trade trade = _lastTrade.Clone() as Trade;
				KeyValuePair<decimal, decimal> bid = Buyings.FirstOrDefault();
				KeyValuePair<decimal, decimal> ask = Sales.LastOrDefault();
				Quotation last = new Quotation {
					Bid = bid.Key,
					BidVolume = bid.Value,
					Ask = ask.Key,
					AskVolume = ask.Value,
					Last = trade.Price,
					LastVolume = trade.Volume,
				};
				callback.SafeInvoke<Quotation>(last);
			}
		}

		private void CandleSubscription(object data)
		{
			Tuple<Action<Candle>, TimeSpan, Guid> threadData = data as Tuple<Action<Candle>, TimeSpan, Guid>;
			Action<Candle> callback = threadData.Item1;
			TimeSpan interval = threadData.Item2;
			Guid receiver = threadData.Item3;

			DateTimeOffset lastSend = DateTimeOffset.UtcNow;
			Candle last = null;
			while (_candleSubsribers.ContainsKey(receiver))
			{
				lock (_tradeLock) Monitor.Wait(_tradeLock, TimeSpan.FromSeconds(1));
				if (_lastTrade == null) continue;

				Trade trade = _lastTrade.Clone() as Trade;

				if (last == null) last = new Candle { Open = trade.Price, Min = trade.Price, Max = trade.Price };

				last.Min = Math.Min(last.Min, trade.Price);
				last.Max = Math.Max(last.Max, trade.Price);
				last.Volume += trade.Volume;

				if (DateTimeOffset.UtcNow - lastSend > interval)
				{
					lastSend = DateTimeOffset.UtcNow;
					last.Close = trade.Price;
					callback.SafeInvoke<Candle>(last);
					last = null;
				}
			}
		}


		#endregion
	}
}
